#include "USART.hpp"
#include "System.hpp"
#include "gpio/GPIO.hpp"

struct SerialPortResource {
    ST::SerialPort *instance;
    USART_Register_t *reg;
    System::ClockChannel clock_channel;
    System::InterruptChannel interrupt_channel;
    char *tx_buffer;
    char *rx_buffer;
    int tx_buffer_ptr;
    int rx_buffer_ptr;
    int tx_buffer_remain;
    int rx_buffer_remain;
    struct {
        SerialPort::EventCallback transmit_complete_callback;
        SerialPort::EventCallback data_received_callback;
        SerialPort::EventCallback idle_callback;
    } event_callbacks;

    struct BindedPin {
        int port_number;
        int pin_number;
        int alternate_function_channel;
    };

    struct BindedPins {
        struct BindedPin ck;
        struct BindedPin txd;
        struct BindedPin rxd;
        struct BindedPin cts;
        struct BindedPin rts;
    };

    struct BindedPins binded_pins[4];
} serial_port_resources[] = {
    /* USART1. */
    [0] = {
        .instance = nullptr,
        .reg = (USART_Register_t*)0x40011000,
        .clock_channel = System::ClockChannel::USART1,
        .interrupt_channel = System::InterruptChannel::USART1,
        .tx_buffer = nullptr,
        .rx_buffer = nullptr,
        .tx_buffer_ptr = 0,
        .rx_buffer_ptr = 0,
        .tx_buffer_remain = 0,
        .rx_buffer_remain = 0,
        .event_callbacks = {
            .transmit_complete_callback = nullptr,
            .data_received_callback = nullptr,
            .idle_callback = nullptr,
        },
        .binded_pins = {
            [0] = {
                .ck = {.port_number = 0,.pin_number = 8,.alternate_function_channel = 7,},      /* CK = PA8. */
                .txd = {.port_number = 0,.pin_number = 9,.alternate_function_channel = 7,},     /* TXD = PA9. */
                .rxd = {.port_number = 0,.pin_number = 10,.alternate_function_channel = 7,},    /* RXD = PA10. */
                .cts = {.port_number = 0,.pin_number = 11,.alternate_function_channel = 7,},    /* CTS = PA11. */
                .rts = {.port_number = 0,.pin_number = 12,.alternate_function_channel = 7,},    /* RTS = PA12. */
            },
            [1] = {
                .ck = {.port_number = -1,.pin_number = -1,.alternate_function_channel = 7,},
                .txd = {.port_number = 1,.pin_number = 6,.alternate_function_channel = 7,},     /* TXD = PB6. */
                .rxd = {.port_number = 1,.pin_number = 7,.alternate_function_channel = 7,},     /* RXD = PB7. */
            },
        },
    },
    /* USART2. */
    [1] = {
        .instance = nullptr,
        .reg = (USART_Register_t*)0x40004400,
        .clock_channel = System::ClockChannel::USART2,
        .interrupt_channel = System::InterruptChannel::USART2,
        .tx_buffer = nullptr,
        .rx_buffer = nullptr,
        .tx_buffer_ptr = 0,
        .rx_buffer_ptr = 0,
        .tx_buffer_remain = 0,
        .rx_buffer_remain = 0,
        .event_callbacks = {
            .transmit_complete_callback = nullptr,
            .data_received_callback = nullptr,
            .idle_callback = nullptr,
        },
        .binded_pins = {
            [0] = {
                .ck = {.port_number = 0,.pin_number = 4,.alternate_function_channel = 7,},      /* CK = PA4. */
                .txd = {.port_number = 0,.pin_number = 2,.alternate_function_channel = 7,},     /* TXD = PA2. */
                .rxd = {.port_number = 0,.pin_number = 3,.alternate_function_channel = 7,},     /* RXD = PA3. */
                .cts = {.port_number = 0,.pin_number = 0,.alternate_function_channel = 7,},     /* CTS = PA0. */
                .rts = {.port_number = 0,.pin_number = 1,.alternate_function_channel = 7,},     /* RTS = PA1. */
            },
            [1] = {
                .ck = {.port_number = 3,.pin_number = 7,.alternate_function_channel = 7,},      /* CK = PD7. */
                .txd = {.port_number = 3,.pin_number = 5,.alternate_function_channel = 7,},     /* TXD = PD5. */
                .rxd = {.port_number = 3,.pin_number = 6,.alternate_function_channel = 7,},     /* RXD = PD6. */
                .cts = {.port_number = 3,.pin_number = 3,.alternate_function_channel = 7,},     /* CTS = PD3. */
                .rts = {.port_number = 3,.pin_number = 4,.alternate_function_channel = 7,},     /* RTS = PD4. */
            },
        },
    },
};

class __SerialPort : public ST::SerialPort {
public:
    __SerialPort(int serial_port_number);
    virtual ~__SerialPort();
    virtual bool open(void);
    virtual bool close(void);
    virtual int read(void *buffer,int size);
    virtual int write(void *buffer,int size);
    virtual int available(void);

    virtual bool setBaudrate(int baudrate);
    virtual int getBaudrate(void);
    virtual bool setDataBits(int data_bits);
    virtual int getDataBits(void);
    virtual bool setParity(Parity parity);
    virtual Parity getParity(void);
    virtual bool setStopBits(StopBits stop_bits);
    virtual StopBits getStopBits(void);
    virtual bool setFlowControl(FlowControl flow_control);
    virtual FlowControl getFlowControl(void);

    virtual bool setTxMode(TransferMode mode);
    virtual TransferMode getTxMode(void);
    virtual bool setRxMode(TransferMode mode);
    virtual TransferMode getRxMode(void);

    virtual bool setPinmap(int pin_map);
    virtual int getPinmap(void);

    virtual bool addEventCallback(EventType event_type,EventCallback callback);
    virtual bool removeEventCallback(EventType event_type);
private:
    int serial_port_number;
    int baudrate;
    int data_bits;
    Parity parity;
    StopBits stop_bits;
    FlowControl flow_control;
    TransferMode tx_mode;
    TransferMode rx_mode;
    uint8_t *dma_tx_buffer;
    uint8_t *dma_rx_buffer;
    int pin_map;
    bool status;
};

__SerialPort::__SerialPort(int serial_port_number){
    this->serial_port_number = serial_port_number;
    this->baudrate = 115200;
    this->data_bits = 8;
    this->parity = Parity::None;
    this->stop_bits = StopBits::One;
    this->flow_control = FlowControl::None;
    this->tx_mode = TransferMode::Polling;
    this->rx_mode = TransferMode::Polling;
    this->status = false;
}

__SerialPort::~__SerialPort(){

}

bool __SerialPort::open(){
    uint32_t control_register_1 = 0x00000000;
    uint32_t control_register_2 = 0x00000000;
    uint32_t control_register_3 = 0x00000000;
    uint32_t baudrate_register = 0x00000000;
    int word_bits;
    /* Open clock channel. */
    if ( !System::setClockChannelStatus(serial_port_resources[this->serial_port_number].clock_channel,true) ) return false;
    /* Open binded pins. */
    ST::Port *port_txd = ST::Port::getInstance(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].txd.port_number);
    port_txd->open();
    ST::Pin *pin_txd = port_txd->getPin(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].txd.pin_number);
    pin_txd->setAlternateFunction(true);
    pin_txd->setDriveType(Pin::DriveType::PushPull);
    pin_txd->setAlternateFunctionChannel(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].txd.alternate_function_channel);
    pin_txd->open();
    ST::Port *port_rxd = ST::Port::getInstance(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].rxd.port_number);
    port_rxd->open();
    ST::Pin *pin_rxd = port_rxd->getPin(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].rxd.pin_number);
    pin_rxd->setAlternateFunction(true);
    pin_rxd->setMode(Pin::Mode::Input);
    pin_rxd->setPullType(Pin::PullType::PullUp);
    pin_rxd->setAlternateFunctionChannel(serial_port_resources[this->serial_port_number].binded_pins[this->pin_map].txd.alternate_function_channel);
    pin_rxd->open();

    /* Configurate data bits. parity and stop bits. */
    word_bits = this->data_bits;
    if ( this->parity != SerialPort::Parity::None ) word_bits += 1;
    switch ( word_bits ){
        case 8: control_register_1 &= ~(0x01 << 12); break;
        case 9: control_register_1 |= (0x01 << 12); break;
    }
    switch ( this->parity ){
        case Parity::None: control_register_1 &= ~(0x01 << 10); break;
        case Parity::Odd:
            control_register_1 |= (0x01 << 10);
            control_register_1 |= (0x01 << 9);
            break;
        case Parity::Even:
            control_register_1 |= (0x01 << 10);
            control_register_1 &= ~(0x01 << 9);
            break;
    }
    control_register_2 &= ~(0b11 << 12);
    switch ( this->stop_bits ){
        case StopBits::Half: control_register_2 |= (0b01 << 12); break;
        case StopBits::One: control_register_2 |= (0b00 << 12); break;
        case StopBits::OneAndHalf: control_register_2 |= (0b11 << 12); break;
        case StopBits::Two: control_register_2 |= (0b10 << 12); break;
    }
    /* Configurate baudrate. */
    unsigned int clock_bus_freqency = System::getClockChannelFrequency(serial_port_resources[this->serial_port_number].clock_channel);
    double f_baudrate_div = clock_bus_freqency / (16 * this->baudrate);
    uint32_t baudrate_div = (unsigned int)f_baudrate_div;
    uint32_t baudrate_frac = ((f_baudrate_div - (double)baudrate_div) * 16);
    baudrate_register = (baudrate_div << 4) | (baudrate_frac & 0x0f);
    /* Configurate flow control. */
    control_register_3 &= ~(0b11 << 8);
    switch ( this->flow_control ){
        case FlowControl::None: break;
        case FlowControl::CTS: control_register_3 |= (0x01 << 9); break;
        case FlowControl::RTS: control_register_3 |= (0x01 << 8); break;
        case FlowControl::CTSAndRTS: control_register_3 |= (0b11 << 8); break;
    }

    /* Enable interrupts. */
    control_register_1 |= (0x01 << 4 ) | (0x01 << 5) | (0x01 << 6);
    void SerialPort_IRQHandler(void *user_data);
    System::addInterruptCallback(serial_port_resources[this->serial_port_number].interrupt_channel,SerialPort_IRQHandler,&(serial_port_resources[this->serial_port_number]));
    System::setInterruptChannelStatus(serial_port_resources[this->serial_port_number].interrupt_channel,true);

    /* Enable usart. */
    control_register_1 |= (0x01 << 13) | (0b11 << 2);
    serial_port_resources[this->serial_port_number].reg->CR3 = control_register_3;
    serial_port_resources[this->serial_port_number].reg->CR2 = control_register_2;
    serial_port_resources[this->serial_port_number].reg->BRR = baudrate_register;
    serial_port_resources[this->serial_port_number].reg->CR1 = control_register_1;

    return true;
}

bool __SerialPort::close(){
    return true;
}

int __SerialPort::read(void *buffer,int size){
    int n = 0;
    uint8_t *uc_buffer = (uint8_t*)buffer;
    if ( this->rx_mode == TransferMode::Polling ){
        for ( n = 0; n < size; n++ ){
            while ( (serial_port_resources[this->serial_port_number].reg->SR & (0x01 << 5)) == 0 );
            uc_buffer[n] = (uint8_t)serial_port_resources[this->serial_port_number].reg->DR;
        }
        if ( serial_port_resources[this->serial_port_number].event_callbacks.data_received_callback != nullptr ){
            serial_port_resources[this->serial_port_number].event_callbacks.data_received_callback();
        }
    }else if ( this->rx_mode == TransferMode::Interrupt ){
        serial_port_resources[this->serial_port_number].rx_buffer = (char*)buffer;
        serial_port_resources[this->serial_port_number].rx_buffer_ptr = 0;
        serial_port_resources[this->serial_port_number].rx_buffer_remain = size;
    }
    return n;
}

int __SerialPort::write(void *buffer,int size){
    int n = 0;
    uint8_t *uc_buffer = (uint8_t*)buffer;
    if ( this->tx_mode == TransferMode::Polling ){
        for ( n = 0; n < size; n++ ){
            while ( (serial_port_resources[this->serial_port_number].reg->SR & (0x01 << 7)) == 0 );
            serial_port_resources[this->serial_port_number].reg->DR = uc_buffer[n];
        }
        if ( serial_port_resources[this->serial_port_number].event_callbacks.transmit_complete_callback != nullptr ){
            serial_port_resources[this->serial_port_number].event_callbacks.transmit_complete_callback();
        }
    }else if ( this->tx_mode == TransferMode::Interrupt ){
        serial_port_resources[this->serial_port_number].tx_buffer = (char*)buffer;
        serial_port_resources[this->serial_port_number].tx_buffer_ptr = 0;
        serial_port_resources[this->serial_port_number].tx_buffer_remain = size;
        serial_port_resources[this->serial_port_number].reg->DR = serial_port_resources[this->serial_port_number].tx_buffer[serial_port_resources[this->serial_port_number].tx_buffer_ptr];
    }
    return n;
}

int __SerialPort::available(void){
    return serial_port_resources[this->serial_port_number].rx_buffer_ptr;
}

bool __SerialPort::setBaudrate(int baudrate){
    this->baudrate = baudrate;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getBaudrate() != baudrate ) return false;
    }
    return false;
}

int __SerialPort::getBaudrate(void){
    if ( !this->status ) return this->baudrate;
    uint32_t baudrate_register = serial_port_resources[this->serial_port_number].reg->BRR;
    uint32_t control_register_1 = serial_port_resources[this->serial_port_number].reg->CR1;
    uint32_t baudrate_div = (baudrate_register >> 4) & 0xfff;
    uint32_t baudrate_frac = (baudrate & 0xf);
    double f_baudrate_div = (double)baudrate_div + ((double)baudrate_frac / 16.0);
    int8_t over8 = ((control_register_1 >> 15) & 0x01);
    unsigned int clock_channel_freq = System::getClockChannelFrequency(serial_port_resources[this->serial_port_number].clock_channel);
    this->baudrate = (unsigned int)((double)clock_channel_freq / (8.0 * (double)(2 - over8) * f_baudrate_div ));
    return this->baudrate;
}

bool __SerialPort::setDataBits(int data_bits){
    if ( data_bits > 8 ) return false;
    this->data_bits = data_bits;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getDataBits() != data_bits ) return false;
    }
    return true;
}

int __SerialPort::getDataBits(void){
    if ( !this->status ) return this->data_bits;
    uint32_t control_register_1 = serial_port_resources[this->serial_port_number].reg->CR1;
    uint32_t parity_bits = (this->getParity() != Parity::None) ? 1 : 0;
    if ( control_register_1 & (0x01 << 12) ){
        this->data_bits = 9;
    }else{
        this->data_bits = 8;
    }
    this->data_bits -= parity_bits;
    return this->data_bits;
}

bool __SerialPort::setParity(Parity parity){
    this->parity = parity;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getParity() != parity ) return false;
    }
    return true;
}

SerialPort::Parity __SerialPort::getParity(void){
    if ( !this->status ) return this->parity;
    uint32_t control_register_1 = serial_port_resources[this->serial_port_number].reg->CR1;
    control_register_1 = ((control_register_1 >> 9) & 0b11);
    switch ( control_register_1 ){
        case 0b00:
        case 0b01: this->parity = Parity::None; break;
        case 0b10: this->parity = Parity::Even; break;
        case 0b11: this->parity = Parity::Odd; break;
    }
    return this->parity;
}

bool __SerialPort::setStopBits(StopBits stop_bits){
    this->stop_bits = stop_bits;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getStopBits() != stop_bits ) return false;
    }
    return true;
}

SerialPort::StopBits __SerialPort::getStopBits(void){
    if ( !this->status ) return this->stop_bits;
    uint32_t control_register_2 = serial_port_resources[this->serial_port_number].reg->CR2;
    control_register_2 = ((control_register_2 >> 12) & 0b11);
    switch ( control_register_2 ){
        case 0b00: this->stop_bits = StopBits::One; break;
        case 0b01: this->stop_bits = StopBits::Half; break;
        case 0b10: this->stop_bits = StopBits::Two; break;
        case 0b11: this->stop_bits = StopBits::OneAndHalf; break;
    }
    return this->stop_bits;
}

bool __SerialPort::setFlowControl(FlowControl flow_control){
    this->flow_control = flow_control;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getFlowControl() != flow_control ) return false;  
    }
    return true;
}

SerialPort::FlowControl __SerialPort::getFlowControl(void){
    if ( !this->status ) return this->flow_control;
    uint32_t control_register_3 = serial_port_resources[this->serial_port_number].reg->CR3;
    control_register_3 = ((control_register_3 >> 8) & 0x03);
    switch ( control_register_3 ){
        case 0b00: this->flow_control = FlowControl::None; break;
        case 0b01: this->flow_control = FlowControl::RTS; break;
        case 0b10: this->flow_control = FlowControl::CTS; break;
        case 0b11: this->flow_control = FlowControl::CTSAndRTS; break;
    }
    return this->flow_control;
}

bool __SerialPort::setTxMode(TransferMode mode){
    this->tx_mode = mode;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getTxMode() != mode ) return false;
    }
    return true;
}

ST::SerialPort::TransferMode __SerialPort::getTxMode(void){
    return this->tx_mode;
}

bool __SerialPort::setRxMode(TransferMode mode){
    this->rx_mode = mode;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getRxMode() != mode ) return false;
    }
    return true;
}

ST::SerialPort::TransferMode __SerialPort::getRxMode(void){
    return this->rx_mode;
}

bool __SerialPort::setPinmap(int pin_map){
    this->pin_map = pin_map;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getPinmap() != pin_map ) return false;
    }
    return true;
}

int __SerialPort::getPinmap(void){
    return this->pin_map;
}

bool __SerialPort::addEventCallback(EventType event_type,EventCallback callback){
    switch ( event_type ){
        case EventType::Idle: serial_port_resources[this->serial_port_number].event_callbacks.idle_callback = callback; break;
        case EventType::DataReceived: serial_port_resources[this->serial_port_number].event_callbacks.data_received_callback = callback; break;
        case EventType::TransmitComplete: serial_port_resources[this->serial_port_number].event_callbacks.transmit_complete_callback = callback; break;
        default: return false;
    }
    return true;
}

bool __SerialPort::removeEventCallback(EventType event_type){
    switch ( event_type ){
        case EventType::Idle: serial_port_resources[this->serial_port_number].event_callbacks.idle_callback = nullptr; break;
        case EventType::DataReceived: serial_port_resources[this->serial_port_number].event_callbacks.data_received_callback = nullptr; break;
        case EventType::TransmitComplete: serial_port_resources[this->serial_port_number].event_callbacks.transmit_complete_callback = nullptr; break;
        default: return false;
    }
    return true;
}

ST::SerialPort *ST::SerialPort::getInstance(int serial_port_number){
    if ( serial_port_number >= (sizeof(serial_port_resources) / sizeof(SerialPortResource)) ) return nullptr;
    if ( serial_port_resources[serial_port_number].instance == nullptr ){
        serial_port_resources[serial_port_number].instance = new __SerialPort(serial_port_number);
    }
    return serial_port_resources[serial_port_number].instance;
}

bool ST::SerialPort::freeInstance(ST::SerialPort *instance){
    if ( instance == nullptr ) return false;
    for ( int index = 0; index < (sizeof(serial_port_resources) / sizeof(SerialPortResource)); index++ ){
        if ( serial_port_resources[index].instance == instance ){
            if ( !serial_port_resources[index].instance->close() ) return false;
            delete serial_port_resources[index].instance;
            serial_port_resources[index].instance = nullptr;
            return true;
        }
    }
    return false;
}

void SerialPort_IRQHandler(void *user_data){
    volatile uint32_t temp;
    SerialPortResource *serial_port_resource = (SerialPortResource*)user_data;
    if ( serial_port_resource->reg->SR & (0x01 << 4) ){
        /* IDLE. */
        if ( serial_port_resource->event_callbacks.idle_callback != nullptr ){
            serial_port_resource->event_callbacks.idle_callback();
        }
        temp = serial_port_resource->reg->DR;
    }
    if ( serial_port_resource->reg->SR & (0x01 << 5) ){
        /* RXNE. */
        if ( serial_port_resource->rx_buffer != nullptr ){
            if ( serial_port_resource->rx_buffer_remain > 0 ){
                uint8_t byte = serial_port_resource->reg->DR;
                serial_port_resource->rx_buffer[serial_port_resource->rx_buffer_ptr++] = byte;
                serial_port_resource->rx_buffer_remain -= 1;
            }
            if ( serial_port_resource->rx_buffer_remain <= 0 ){
                /* Data received. */
                serial_port_resource->rx_buffer = nullptr;
                if ( serial_port_resource->event_callbacks.data_received_callback != nullptr ){
                    serial_port_resource->event_callbacks.data_received_callback();
                }
            }
        }
        /* Clear flag. */
        serial_port_resource->reg->SR &= ~(0x01 << 5);
    }
    if ( serial_port_resource->reg->SR & (0x01 << 6) ){
        /* TC. */
        if ( serial_port_resource->tx_buffer != nullptr ){
            if ( serial_port_resource->tx_buffer_remain > 0 ){
                serial_port_resource->tx_buffer_ptr += 1;
                serial_port_resource->tx_buffer_remain -= 1;
                if ( serial_port_resource->tx_buffer_remain <= 0 ){
                    /* Transmit Complete. */ 
                    serial_port_resource->tx_buffer = nullptr;
                    if ( serial_port_resource->event_callbacks.transmit_complete_callback != nullptr ){
                        serial_port_resource->event_callbacks.transmit_complete_callback();
                    }
                }else{
                    /* Keep send. */
                    uint8_t byte = serial_port_resource->tx_buffer[serial_port_resource->tx_buffer_ptr];
                    serial_port_resource->reg->DR = byte;
                }
            }
        }
        /* Clear flag. */
        serial_port_resource->reg->SR &= ~(0x01 << 6);
    }
}
